{ PH_Dev }

Published on

Zod v4 實戰攻略 - TypeScript-First 表單驗證利器

Authors
  • avatar
    Name
    Penghua Chen(PH)
    Twitter

本文摘要

Zod 表單驗證函式庫是近年來很熱門的表單驗證函式庫,主要可用於前端表單欄位驗證,或者是 API Request 請求時驗證欄位使用。

筆者在最近一個專案中使用到,所以撰寫筆記記錄使用心得。

本篇文章採用 Zod v4 作為文章撰寫使用的版本。

本篇文章重點:

  1. 為什麼覺得 Zod 值得為它撰寫文章?
  2. 基礎使用方式
  3. 前端 Demo - 以使用者建立表單為例 (使用 Vue3)

Zod 基礎使用方式

four-critical-concepts-for-zod.png

Zod 核心使用概念:

  1. 定義 schema (define schema)
  2. 解析 data (parsing data)
  3. 錯誤處理 (handling errors)
  4. 型別推導(Inferring types)

1. 定義 schema (define schema)

定義 schema 的意義是跟 Zod 說:「嘿 Zod ! 我要提供一組規則讓你知道,你必須幫我按照這個規則確認是否通過/失敗」。

以建立使用者資料為例,我們定義以下要提供的規則:

  1. name: 必填,使用者姓名,預設值為空字串
  2. password: 必填,使用者密碼,密碼長度至少為 8 碼,預設值為空字串
  3. age: 選填,使用者年齡,只能輸入數字,預設值為 null

基於上面提到的情境與規則,我們可以撰寫以下 schema:

import * as z from "zod";

const UserSchema = z.object({
	name: z.string().min(1, '使用者姓名為必填'),
	password: z.string().min(8, '密碼長度至少需要 8 碼') ,
	age: z.number().nullable().optional(),
});

2. 解析 data (parsing data)

緊接著,透過 .parse 以及傳入的 schema 定義所回傳的 user 物件進行 data 解析,如果驗證通過,會直接 return 整個 user 物件:

const UserSchema = z.object({
	name: z.string().min(1, '使用者姓名為必填'),
	password: z.string().min(8, '密碼長度至少需要 8 碼') ,
	age: z.number().nullable().optional(),
});

const user = {
	name: "PH",
	password: "12345678",
	age: 30
};

UserSchema.parse(user); // 如果驗證通過,會直接 return 整個 user 物件

那如果驗證沒有通過呢?

例如接下來的這個情境: username 忘記填寫了!

接著就會進入到錯誤處理步驟

3. 錯誤處理 (handling errors)

如同前面提到, username 忘記填寫,此時會發生錯誤,並拋出 ZodError 實例(instance):

const user = {
	password: "12345678",
	age: 30
};

UserSchema.parse(user); // 拋出 ZodError 錯誤

如果習慣使用 try/catch 實作錯誤處理的讀者,可以透過 catch(error) 進行錯誤捕捉(capture)

try {
	const UserSchema = z.object({
		name: z.string().min(1, '使用者姓名為必填'),
		password: z.string().min(8, '密碼長度至少需要 8 碼') ,
		age: z.number().nullable().optional(),
	});

	const user = {
		password: "12345678",
		age: 30
	};

	UserSchema.parse(user);
} catch (error) {
	// 需要確認是 ZodError 物件
	if (error instanceof z.ZodError) {
		console.log('error.issues', error.issues);
	}
}

如果不想要使用 try/catch 方式實作,Zod 也提供透過 safeParse 的方式 return 一個驗證結果物件(plain result object),可以透過 .error 存取這個物件:

const UserSchema = z.object({
	name: z.string().min(1, '使用者姓名為必填'),
	password: z.string().min(8, '密碼長度至少需要 8 碼') ,
	age: z.number().nullable().optional(),
});

const user = {
	password: "12345678",
	age: 30
};

const validationResult = UserSchema.safeParse(user);
if (validationResult.sucess) {
	console.log('驗證通過', validationResult.data);
} else {
	console.log('驗證失敗', validationResult.error);
}

4. 型別推導(Inferring types)

Zod 提供了 z.infer<> utility,可以透過它提取資料型別(extract the inferred type)

==意思是當你定義好了 schema,你同時也定義好了它的type,大幅降低重複的型別定義。==

讓我們修改前面的程式碼:

const UserSchema = z.object({
	name: z.string().min(1, '使用者姓名為必填'),
	password: z.string().min(8, '密碼長度至少需要 8 碼') ,
	age: z.number().nullable().optional(),
});

// 自動提取 Zod Object 的 UserSchema 資料型別
type User = z.infer<typeof UserSchema>;

const user = {
	password: "12345678",
	age: 30
};

const validationResult = UserSchema.safeParse(user);
if (validationResult.sucess) {
	console.log('驗證通過', validationResult.data);
} else {
	console.log('驗證失敗', validationResult.error);
}

有時候,input 與 output 的型別不總是相同,以文件中提到的例子為例:

const mySchema = z.string().transform((val) => val.length);

type MySchemaIn = z.input<typeof mySchema>;
// => string

type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema>
// number

input 要求為 string 型別,但是 output 則要求是 number 型別。 這邊使用到關鍵語法 .transform 做到將輸入為 string 型別的值,轉為 number 型別的值

data-validation-flow.png

Demos

我喜歡在學習某個工具時,同時也透過範例來驗證目前的理解對不對,所以這邊提供了前端、後端的 Demo Projects 提供大家參考。

前端 Demo

前往 Demo Project 👉👉👉👉👉

desktop-demo-imagemobile-demo-image

參考資源